package lichunUtil;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Manage the read and write of the table like file.
 * @author lichunyang
 */
public class ManageTableFile {
	/**
	 * Note: column number -1 indicates the line number.
	 * @param file 
	 * 		the file path
	 * @param col_key 
	 * @param col_value 
	 * @param settingString the simple settings
	 * 		-e encoding
	 * 		-o omit the first several lines
	 * 		-s the separator
	 * 		-l specify the line number of the first line (if -o, then it is for the second line)
	 * @return a mapping
	 */
	static public Map<String, String> readMapping (String file, int col_key, int col_value, String settingString) {
		Map<String, String> res = new HashMap<String, String>();
		simSettings settings = getSetting(settingString);
		
		try {
			BufferedReader br = new BufferedReader(	new InputStreamReader(
					new FileInputStream(file), settings.encode));
			
			//readMapping(br, col_key, col_value, settingString);
			for (int i=0;i<settings.omitLines;i++)
				br.readLine();
			Integer lineNum =settings.linenum;
			while (true) {
				String in = br.readLine();
				if (in == null)
					break;
				//System.out.println(in);
				String parts[] = in.split(settings.separator);
				String key = "";
				String value = "";
				if (col_key==-1)
					key = lineNum.toString();
				else
					key = parts[col_key];
				if (col_value==-1)
					value = lineNum.toString();
				else
					value = parts[col_value];
				res.put(key, value);
				
				lineNum++;
			}
			
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
		
		return res;
	}
	
	/**
	 * Note: column number -1 indicates the line number.
	 * @param br 
	 * 		the BufferedReader
	 * @param col_key 
	 * @param col_value 
	 * @param settingString the simple settings
	 * 		-o omit the first several lines
	 * 		-s the separator
	 * 		-l specify the line number of the first line (if -o, then it is for the second line)
	 * @return a mapping
	 */
	static public Map<String, String> readMapping (BufferedReader br, int col_key, int col_value, String settingString) {
		Map<String, String> res = new HashMap<String, String>();
		simSettings settings = getSetting(settingString);
		
		try {
			for (int i=0;i<settings.omitLines;i++)
				br.readLine();
			Integer lineNum =settings.linenum;
			while (true) {
				String in = br.readLine();
				if (in == null)
					break;
				//System.out.println(in);
				String parts[] = in.split(settings.separator);
				String key = "";
				String value = "";
				if (col_key==-1)
					key = lineNum.toString();
				else
					key = parts[col_key];
				if (col_value==-1)
					value = lineNum.toString();
				else
					value = parts[col_value];
				res.put(key, value);
				
				lineNum++;
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
		return res;
	}
		
	
	/**
	 * A simple write.
	 * Two column mapping
	 * @param file
	 * @param table
	 * @param settingString
	 */
	static public void writeTable (String file, Map<? extends Object, ? extends Object> table, String settingString) {
		simSettings settings = getSetting(settingString);
		
		try {
			PrintStream ps = new PrintStream(file);
			
			for (Entry<? extends Object, ? extends Object> e : table.entrySet()) {
				ps.printf("%s%s%s%n", 
						e.getKey().toString(), 
						settings.separator, 
						e.getValue().toString());
			}
			
			ps.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
		
	}
	
	/**
	 * @param ps
	 * @param table
	 * @param settingString
	 */
	static public void writeTable (PrintStream ps, Map<? extends Object, ? extends Object> table, String settingString) {
		simSettings settings = getSetting(settingString);
		for (Entry<? extends Object, ? extends Object> e : table.entrySet()) {
			ps.printf("%s%s%s%n", 
					e.getKey().toString(), 
					settings.separator, 
					e.getValue().toString());
		}
	}
	
	/**
	 * Output a big map.
	 * @param file
	 * @param settingString
	 * @param objs
	 * 		The objects should be ArrayList or Object[] or int[] ...
	 */
	static public void writeTable (String file, String settingString, Object... objs) {
		writeTable(file, settingString, null, objs);
	}
	
		
	/**
	 * Output a big map.
	 * @param file
	 * @param settingString
	 * @param filterMatched
	 * 		a regular expression of the lines that should be filtered
	 * @param objs
	 * 		The objects should be ArrayList or Object[] or int[] ...
	 */
	static public void writeTable (String file, String settingString, String filterMatched, Object... objs) {
		simSettings settings = getSetting(settingString);
		
		Pattern p = null;
		if (filterMatched!=null)
			p = Pattern.compile(filterMatched);
		
		Object[][] table = new Object[objs.length][];
		int size =0;
		for (int i=0;i<objs.length;i++) {
			table[i] = format2array(objs[i]);
			if (i==0)
				size = table[i].length;
			else {
				if (size!=table[i].length) {
					System.err.println("column "+i+" not same size as column 0");
					size = Math.min(size, table[i].length);
				}
			}
		}
		
		try {
			PrintStream ps = new PrintStream(file);
			for (int pos=0;pos<size;pos++) {
				String towrite = "";
				for (int i=0;i<table.length;i++) {
					if (i!=0) towrite += settings.separator;
					towrite += table[i][pos].toString();
				}
				if (towrite==null) break;
				Matcher m = null;
				if (p!=null)
					m = p.matcher(towrite);
				if (m==null || m.matches())
					ps.println(towrite);
				//else System.out.println("omitted:\t"+towrite);
			}
			
			ps.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
	}
	
	/**
	 * @param ps
	 * @param settingString
	 * @param filterMatched
	 * 		a regular expression of the lines that should be filtered
	 * @param objs
	 * 		The objects should be ArrayList or Object[] or int[] ...
	 */
	static public void writeTable (PrintStream ps, String settingString, String filterMatched, Object... objs) {
		simSettings settings = getSetting(settingString);
		
		Pattern p = null;
		if (filterMatched!=null)
			p = Pattern.compile(filterMatched);
		
		Object[][] table = new Object[objs.length][];
		int size =0;
		for (int i=0;i<objs.length;i++) {
			table[i] = format2array(objs[i]);
			if (i==0)
				size = table[i].length;
			else {
				if (size!=table[i].length) {
					System.err.println("column "+i+" not same size as column 0");
					size = Math.min(size, table[i].length);
				}
			}
		}
		
		for (int pos=0;pos<size;pos++) {
			String towrite = "";
			for (int i=0;i<table.length;i++) {
				if (i!=0) towrite += settings.separator;
				towrite += table[i][pos].toString();
			}
			if (towrite==null) break;
			Matcher m = null;
			if (p!=null)
				m = p.matcher(towrite);
			if (m==null || m.matches())
				ps.println(towrite);
			//else System.out.println("omitted:\t"+towrite);
		}
	}
	
	static private Object[] format2array (Object ori) {
		Object[] res = null;
		if (ori instanceof Object[]) {
			res = (Object[]) ori;
		}
		else if (ori instanceof ArrayList) {
			ArrayList list = (ArrayList) ori;
			res = (Object[]) list.toArray(new Object[list.size()]);
		}
		else if (ori instanceof int[]) {
			int[] temp = (int[]) ori;
			res = new Object[temp.length];
			for (int i=0;i<temp.length;i++)
				res[i] = new Integer(temp[i]);
		}
		else if (ori instanceof double[]) {
			double[] temp = (double[]) ori;
			res = new Object[temp.length];
			for (int i=0;i<temp.length;i++)
				res[i] = new Double(temp[i]);
		}
		else if (ori instanceof boolean[]) {
			boolean[] temp = (boolean[]) ori;
			res = new Object[temp.length];
			for (int i=0;i<temp.length;i++)
				res[i] = new Boolean(temp[i]);
		}
		else 
			System.err.println("type error");
		return res;
	}
	
	/**
	 * Note: column number -1 indicates the line number.
	 * @param file 
	 * 		the file path
	 * @param col
	 * @param settingString the simple settings
	 * 		-e encoding
	 * 		-o omit the first several lines
	 * 		-s the separator
	 * 		-l specify the line number of the first line (if -o, then it is for the second line)
	 * @return an ArrayList<String>
	 */
	static public ArrayList<String> readArrayList(String file, int col, String settingString) {
		ArrayList<String> res = new ArrayList<String>();
		simSettings settings = getSetting(settingString);
		
		try {
			BufferedReader br = new BufferedReader(	new InputStreamReader(
					new FileInputStream(file), settings.encode));
			for (int i=0;i<settings.omitLines;i++)
				br.readLine();
			Integer lineNum =settings.linenum;
			while (true) {
				String in = br.readLine();
				if (in == null)
					break;
				//System.out.println(in);
				String parts[] = in.split(settings.separator);
				String value = "";
				if (col==-1)
					value = lineNum.toString();
				else
					value = parts[col];
				res.add(value);
				
				lineNum++;
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
		return res;
	}
	
	/**
	 * Note: column number -1 indicates the line number.
	 * @param br
	 * @param col
	 * @param settingString the simple settings
	 * 		-e encoding
	 * 		-o omit the first several lines
	 * 		-s the separator
	 * 		-l specify the line number of the first line (if -o, then it is for the second line)
	 * @return
	 */
	static public ArrayList<String> readArrayList(BufferedReader br, int col, String settingString) 
		throws Exception {
		ArrayList<String> res = new ArrayList<String>();
		simSettings settings = getSetting(settingString);
		for (int i=0;i<settings.omitLines;i++)
			br.readLine();
		Integer lineNum =settings.linenum;
		while (true) {
			String in = br.readLine();
			if (in == null)
				break;
			//System.out.println(in);
			String parts[] = in.split(settings.separator);
			String value = "";
			if (col==-1)
				value = lineNum.toString();
			else
				value = parts[col];
			res.add(value);
			
			lineNum++;
		}
		return res;
	}
	
	/**
	 * Note: column number -1 indicates the line number.
	 * @param file 
	 * 		the file path
	 * @param col_key
	 * @param col_value
	 * @param settingString the simple settings
	 * 		-e encoding
	 * 		-o omit the first several lines
	 * 		-s the separator
	 * 		-l specify the line number of the first line (if -o, then it is for the second line)
	 * @return an ArrayList<WrapStringValue>
	 */
	static public ArrayList<WrapStringValue> readWrapStringValue (String file, int col_key, int col_value, String settingString) {
		ArrayList<WrapStringValue> res = new ArrayList<WrapStringValue>();
		simSettings settings = getSetting(settingString);
		
		try {
			BufferedReader br = new BufferedReader(	new InputStreamReader(
					new FileInputStream(file), settings.encode));
			for (int i=0;i<settings.omitLines;i++)
				br.readLine();
			Integer lineNum =settings.linenum;
			while (true) {
				String in = br.readLine();
				if (in == null)
					break;
				//System.out.println(in);
				String parts[] = in.split(settings.separator);
				String key = "";
				String value = "";
				if (col_key==-1)
					key = lineNum.toString();
				else
					key = parts[col_key];
				if (col_value==-1)
					value = lineNum.toString();
				else
					value = parts[col_value];
				double doublev = Double.parseDouble(value);
				res.add(new WrapStringValue(key, doublev));
				
				lineNum++;
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
		return res;
	}
	
	/**
	 * Note: column number -1 indicates the line number.
	 * @param br 
	 * @param col_key
	 * @param col_value
	 * @param settingString the simple settings
	 * 		-e encoding
	 * 		-o omit the first several lines
	 * 		-s the separator
	 * 		-l specify the line number of the first line (if -o, then it is for the second line)
	 * @return an ArrayList<WrapStringValue>
	 */
	static public ArrayList<WrapStringValue> readWrapStringValue (BufferedReader br, int col_key, int col_value, String settingString) 
		throws Exception {
		ArrayList<WrapStringValue> res = new ArrayList<WrapStringValue>();
		simSettings settings = getSetting(settingString);
		
		for (int i=0;i<settings.omitLines;i++)
			br.readLine();
		Integer lineNum =settings.linenum;
		while (true) {
			String in = br.readLine();
			if (in == null)
				break;
			//System.out.println(in);
			String parts[] = in.split(settings.separator);
			String key = "";
			String value = "";
			if (col_key==-1)
				key = lineNum.toString();
			else
				key = parts[col_key];
			if (col_value==-1)
				value = lineNum.toString();
			else
				value = parts[col_value];
			double doublev = Double.parseDouble(value);
			res.add(new WrapStringValue(key, doublev));
			
			lineNum++;
		}
		
		return res;
	}
	
	static private class simSettings {
		String encode = "utf-8";
		int omitLines =0;
		String separator = "\t";
		int linenum =0;
	}
	static private simSettings getSetting (String settingString) {
		Map<String, String> para = AnalyzeParameter.simAnalyze(settingString);
		
		simSettings res = new simSettings();
		if (para.containsKey("e")) {
			res.encode = para.get("e");
		}
		if (para.containsKey("s")) {
			res.separator = para.get("s");
		}
		if (para.containsKey("o")) {
			res.omitLines = Integer.parseInt(para.get("o"));
		}
		if (para.containsKey("l")) {
			int l = Integer.parseInt(para.get("l"));
			res.linenum = l;
		}
		return res;
	}
}
